using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace ExplodingCube
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Game
    {
        private GraphicsDeviceManager graphicsDeviceManager;
        private SpriteBatch spriteBatch;
        private bool isKeyEscapeDown;
        private bool isKeySpaceDown;
        private bool isKeyDelDown;
        private bool isAnimationLocked;
        private TimeSpan elapsedUpdateTime;
        private int cubeSize;
        private BasicEffect basicEffect;
        private Model cube;
        private List<Vector3> cubeColors;
        private List<Matrix> cubeStepRotations;
        private List<Matrix> cubeRotations;
        private List<Matrix> cubeTranslations;
        private List<Vector3> cubeVelocities;

        public Game1()
        {
            this.graphicsDeviceManager = new GraphicsDeviceManager( this );
            this.Content.RootDirectory = "Content";

            this.IsMouseVisible = true;

            this.cubeSize = 5;
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            this.basicEffect = new BasicEffect( this.GraphicsDevice, null );
            this.basicEffect.World = Matrix.CreateTranslation( new Vector3( -0.1f, -0.5f, -0.1f ) ) * Matrix.CreateScale( this.cubeSize );
            this.basicEffect.View = Matrix.CreateLookAt( new Vector3( 15, 10, 10 * this.cubeSize ), Vector3.Zero, Vector3.Up );
            this.basicEffect.Projection = Matrix.CreatePerspectiveFieldOfView( MathHelper.ToRadians( 45 ),
                                                                               (float) this.Window.ClientBounds.Width /
                                                                               (float) this.Window.ClientBounds.Height,
                                                                               0.1f,
                                                                               2 * ( 10 * this.cubeSize ) );

            this.cubeColors = new List<Vector3>();
            this.cubeColors.Add( Color.Black.ToVector3() );

            this.cubeStepRotations = new List<Matrix>();
            this.cubeStepRotations.Add( Matrix.Identity );

            this.cubeRotations = new List<Matrix>();
            this.cubeRotations.Add( Matrix.Identity );

            this.cubeTranslations = new List<Matrix>();
            this.cubeTranslations.Add( Matrix.Identity );

            this.cubeVelocities = new List<Vector3>();
            this.cubeVelocities.Add( Vector3.Zero );

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            this.spriteBatch = new SpriteBatch( this.GraphicsDevice );

            this.cube = this.Content.Load<Model>("cube");
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update( GameTime gameTime )
        {
            this.elapsedUpdateTime += gameTime.ElapsedGameTime;

            if ( this.elapsedUpdateTime.Milliseconds > 25 )
            {
                this.elapsedUpdateTime = TimeSpan.Zero;

                int counter = 0;
                foreach ( Vector3 velocity in this.cubeVelocities )
                {
                    this.cubeRotations[counter] *= this.cubeStepRotations[counter];
                    this.cubeTranslations[counter] *= Matrix.CreateTranslation( velocity );

                    counter++;
                }
            }

            bool shouldReset = false;

            // Allows the game to exit
            if ( Keyboard.GetState().IsKeyUp( Keys.Escape ) &&
                 this.isKeyEscapeDown )
            {
                this.Exit();
            }
            if ( Keyboard.GetState().IsKeyUp( Keys.Delete ) &&
                 this.isKeyDelDown )
            {
                shouldReset = true;
            }
            if ( Keyboard.GetState().IsKeyUp( Keys.Space ) &&
                 this.isKeySpaceDown &&
                 !this.isAnimationLocked )
            {
                this.isAnimationLocked = true;

                this.basicEffect.World = Matrix.Identity;

                Random random = new Random();
                this.cubeColors = new List<Vector3>();
                this.cubeStepRotations = new List<Matrix>();
                this.cubeRotations = new List<Matrix>();
                this.cubeTranslations = new List<Matrix>();
                this.cubeVelocities = new List<Vector3>();

                float halfCubeSize = this.cubeSize / 2f;
                for ( float z = 0; z < this.cubeSize; z++ )
                {
                    for ( float y = 0; y < this.cubeSize; y++ )
                    {
                        for ( float x = 0; x < this.cubeSize; x++ )
                        {
                            if ( x == halfCubeSize && y == halfCubeSize &&
                                 z == halfCubeSize )
                            {
                                this.cubeColors.Add( Color.Black.ToVector3() );
                                this.cubeStepRotations.Add(
                                        Matrix.CreateRotationZ( MathHelper.ToRadians( (float) random.NextDouble() * random.Next( 2 ) == 0 ? 1 : -1 ) ) *
                                        Matrix.CreateRotationY( MathHelper.ToRadians( (float) random.NextDouble() * random.Next( 2 ) == 0 ? 1 : -1 ) ) *
                                        Matrix.CreateRotationX( MathHelper.ToRadians( (float) random.NextDouble() * random.Next( 2 ) == 0 ? 1 : -1 ) ) );
                                this.cubeRotations.Add( Matrix.Identity );
                            }
                            else
                            {
                                this.cubeColors.Add( new Vector3( 1f, ( x + y + z ) / ( this.cubeSize + this.cubeSize + this.cubeSize ), 0 ) );
                                this.cubeStepRotations.Add(
                                        Matrix.CreateRotationZ( MathHelper.ToRadians( (float) random.NextDouble() * random.Next( 2 ) == 0 ? 1 : -1 ) ) *
                                        Matrix.CreateRotationY( MathHelper.ToRadians( (float) random.NextDouble() * random.Next( 2 ) == 0 ? 1 : -1 ) ) *
                                        Matrix.CreateRotationX( MathHelper.ToRadians( (float) random.NextDouble() * random.Next( 2 ) == 0 ? 1 : -1 ) ) );
                                this.cubeRotations.Add( Matrix.Identity );
                            }

                            this.cubeTranslations.Add( Matrix.CreateTranslation( new Vector3( x - halfCubeSize, y - halfCubeSize, z - halfCubeSize ) ) );
                            this.cubeVelocities.Add( new Vector3( x - halfCubeSize, y - halfCubeSize, z - halfCubeSize ) );
                        }
                    }
                }
            }

            this.isKeyEscapeDown = Keyboard.GetState().IsKeyDown( Keys.Escape );
            this.isKeySpaceDown = Keyboard.GetState().IsKeyDown( Keys.Space );
            this.isKeyDelDown = Keyboard.GetState().IsKeyDown( Keys.Delete );

            int cubesInScreen = 0;
            int counter2 = 0;
            foreach (Matrix translation in cubeTranslations)
            {
                Vector3 cubePosition = Vector3.Transform(Vector3.Zero, this.basicEffect.World *
                                           this.cubeTranslations[counter2] * 
                                           this.cubeRotations[counter2]);

                if (cubePosition.X > -400 && cubePosition.X < 400 &&
                    cubePosition.Y > -300 && cubePosition.Y < 300 && 
                    cubePosition.Z > 0.1f && cubePosition.Z < (10 * this.cubeSize))
                {
                    cubesInScreen++;
                }

                counter2++;
            }

            if (shouldReset || cubesInScreen == 0)
            {
                Reset();
            }

            base.Update( gameTime );
        }

        private void Reset()
        {
            this.isAnimationLocked = false;

            this.basicEffect.World = Matrix.CreateTranslation(new Vector3(-0.1f, -0.5f, -0.1f)) * Matrix.CreateScale(this.cubeSize);

            this.cubeColors = new List<Vector3>();
            this.cubeColors.Add(Color.Black.ToVector3());

            this.cubeStepRotations = new List<Matrix>();
            this.cubeStepRotations.Add(Matrix.Identity);

            this.cubeRotations = new List<Matrix>();
            this.cubeRotations.Add(Matrix.Identity);

            this.cubeTranslations = new List<Matrix>();
            this.cubeTranslations.Add(Matrix.Identity);

            this.cubeVelocities = new List<Vector3>();
            this.cubeVelocities.Add(Vector3.Zero);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw( GameTime gameTime )
        {
            this.GraphicsDevice.Clear( Color.CornflowerBlue );

            this.GraphicsDevice.RenderState.CullMode = CullMode.None;

            int counter = 0;
            foreach ( Matrix translation in cubeTranslations )
            {
                foreach ( ModelMesh cubeMesh in cube.Meshes )
                {
                    foreach (BasicEffect cubeEffect in cubeMesh.Effects)
                    {
                        cubeEffect.World = this.basicEffect.World *
                                           this.cubeTranslations[counter] * 
                                           this.cubeRotations[counter];
                        cubeEffect.View = this.basicEffect.View;
                        cubeEffect.Projection = this.basicEffect.Projection;

                        cubeEffect.EnableDefaultLighting();
                        cubeEffect.LightingEnabled = true;
                        cubeEffect.PreferPerPixelLighting = true;
                        cubeEffect.DiffuseColor = Color.White.ToVector3();
                        cubeEffect.DirectionalLight0.Enabled = true;
                        cubeEffect.DirectionalLight0.SpecularColor = this.cubeColors[counter];
                        cubeEffect.DirectionalLight0.DiffuseColor = cubeEffect.DirectionalLight0.SpecularColor;
                    }

                    cubeMesh.Draw();
                }

                counter++;
            }

            base.Draw( gameTime );
        }
    }
}